<?php
/**
 * Copyright 2011  SURFfoundation
 * 
 * This file is part of ESCAPE.
 * 
 * ESCAPE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * ESCAPE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ESCAPE.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information:
 * http://escapesurf.wordpress.com/
 * http://www.surffoundation.nl/
 * 
 * Contact: d.vierkant@utwente.nl
 * 
 * @license http://www.gnu.org/licenses/gpl.html GNU GPLv3
 */
?>
<?php
/**
 * RDF schema document
 * 
 * Represents an RDF schema, provides basic RDFS and OWL support.
 */
class simplerdf_RdfSchemaDocument extends simplerdf_RdfDocument
{
	/**
	 * Construct a new simplerdf_RdfSchemaDocument.
	 * @param string|array $rdfXmlOrSimpleIndex Either a string containing a RDF/XML document or an ARC2 SimpleIndex
	 * @param simplerdf_RdfSchemaDocument $schema RDF Schema (optional)
	 * @param string $base XML base URL (optional)
	 */
	public function __construct($rdfXmlOrSimpleIndex, &$schema = null, $base = '')
	{
		parent::__construct($rdfXmlOrSimpleIndex, $schema, $base);
	}
	
	/**
	 * Create a new (empty) document.
	 * @param string $subject The subject of the resource 
	 * @param simplerdf_RdfSchemaDocument $schema RDF Schema (optional)
	 * @return simplerdf_RdfSchemaDocument The new (empty) resource
	 */
	public static function createRdfSchemaDocument(&$schema = null)
	{
		$index = array();
		
		return new simplerdf_RdfSchemaDocument($index, $schema);
	}
	
	/**
	 * Preprocess the schema. Adds missing owl:inverseOf, rdfs:domain and rdfs:range statements. 
	 */
	public function preprocessSchema()
	{
		// loop over all resources
		foreach($this->resources as $subject => $resource)
		{
			// fetch owl:inverseOf statements 
			$inverseOfObjects = $resource->getObjects('http://www.w3.org/2002/07/owl#inverseOf');
			
			// loop over all owl:inverseOf statements
			foreach($inverseOfObjects as $inverseOfObject)
			{
				// fetch the inverse object
				$inverseOfResource = $this->resources[$inverseOfObject];
				if($inverseOfResource !== null)
				{
					// add an owl:inverseOf statement to the inverse object
					$inverseOfResource->addStatement('http://www.w3.org/2002/07/owl#inverseOf', $subject);
					
					// add rdfs:range statements to the inverse object based on the rdfs:domain of the current object
					$domains = $resource->getObjects('http://www.w3.org/2000/01/rdf-schema#domain');
					if(count($domains) > 0)
					{
						$inverseOfResource->addStatement('http://www.w3.org/2000/01/rdf-schema#range', $domains);
					}
					
					// add rdfs:domain statements to the inverse object based on the rdfs:range of the current object
					$ranges = $resource->getObjects('http://www.w3.org/2000/01/rdf-schema#range');
					if(count($ranges) > 0)
					{
						$inverseOfResource->addStatement('http://www.w3.org/2000/01/rdf-schema#domain', $ranges);
					}
				}
			}
		}
	} 
	
	/**
	 * Get the rdfs:label of a schema resource.
	 * @param string $resourceUri Resource URI
	 * @param string $returnUriIfNotFound If true, the resource URI is returned if the resource does not have a label, otherwise a null will be returned
	 * @return string Resource label
	 */
	public function getResourceLabel($resourceUri, $returnUriIfNotFound = true)
	{
		$label = null;
		
		$resource = $this->getResourceBySubject($resourceUri);
		if($resource !== null)
		{
			$label = $resource->getFirstObject('http://www.w3.org/2000/01/rdf-schema#label');
		}

		if($label === null && $returnUriIfNotFound)
		{
			$label = $resourceUri;
		}
		
		return $label;
	}
	
	/**
	 * Get the rdfs:comment of a schema resource.
	 * @param string $resourceUri Resource URI
	 * @return string Resource comment 
	 */
	public function getResourceComment($resourceUri)
	{
		$comment = null;
		
		$resource = $this->getResourceBySubject($resourceUri);
		if($resource !== null)
		{
			$comment = $resource->getFirstObject('http://www.w3.org/2000/01/rdf-schema#comment');
		}
		
		return $comment;
	}
	
	/**
	 * Gets the owl:inverseOf of a schema resource. The specified property URI will be returned as is for owl:SymmetricProperty type properties.
	 * warning: the owl:inverseOf statement needs to be applied to both sides of the inverses in the schema!
	 * @param string $resourceUri Resource URI
	 * @return string Inverse property URI, null if the property or owl:inverseOf doesn't exist 
	 */
	public function getInversePropertyUri($propertyUri)
	{
		$inversePropertyUri = null;
		
		$property = $this->getResourceBySubject($propertyUri);
		if($property !== null)
		{
			$inversePropertyUri = $property->getFirstObject('http://www.w3.org/2002/07/owl#inverseOf');
		
			if($inversePropertyUri === null)
			{
				if($property->hasRdfType('http://www.w3.org/2002/07/owl#SymmetricProperty'))
				{
					$inversePropertyUri = $propertyUri;
				}
			}
		}
		
		return $inversePropertyUri;
	}

	/**
	 * Get the first (direct) parent property URI.
	 * @param string $propertyUri Property URI
	 * @return string Parent property URI or null
	 */
	public function getFirstParentPropertyUri($propertyUri)
	{
		$parentPropertyUri = null;
		
		$property = $this->getResourceBySubject($propertyUri);
		if($property !== null)
		{
			$parentPropertyUri = $property->getFirstObject('http://www.w3.org/2000/01/rdf-schema#subPropertyOf');
		}
		
		return $parentPropertyUri;
	}
	
	/**
	 * Get the (direct) parent property URIs.
	 * @param string $propertyUri Property URI
	 * @return array Arrary of parent property URIs
	 */
	public function getParentPropertyUris($propertyUri)
	{
		$parentPropertyUris = array();
		
		$property = $this->getResourceBySubject($propertyUri);
		if($property !== null)
		{
			$parentPropertyUris = $property->getObjects('http://www.w3.org/2000/01/rdf-schema#subPropertyOf');
		}
		
		return $parentPropertyUris;
	}
	
	/**
	 * Get all (direct and indirect) parent property URIs as a list. 
	 * @param string $propertyUri Property URI
	 * @return array List of parent property URIs
	 */
	public function getParentPropertyUriList($propertyUri)
	{
		return $this->getChainedSubjectObjectsList('http://www.w3.org/2000/01/rdf-schema#subPropertyOf', $propertyUri);
	}
	
	/**
	 * Get all (direct and indirect) parent property URIs as a tree. 
	 * @param string $propertyUri Property URI
	 * @return array Tree of parent property URIs
	 */
	public function getParentPropertyUriTree($propertyUri)
	{
		return $this->getChainedSubjectObjectsTree('http://www.w3.org/2000/01/rdf-schema#subPropertyOf', $propertyUri);
	}
	
	/**
	 * Get all (direct and indirect) sub property URIs as a list. 
	 * @param string $propertyUri Property URI
	 * @return array List of sub property URIs
	 */
	public function getSubPropertyUriList($propertyUri)
	{
		return $this->getChainedObjectSubjectsList('http://www.w3.org/2000/01/rdf-schema#subPropertyOf', $propertyUri);
	}
	
	/**
	 * Get all (direct and indirect) sub property URIs as a tree. 
	 * @param string $propertyUri Property URI
	 * @return array Tree of sub property URIs
	 */
	public function getSubPropertyUriTree($propertyUri)
	{
		return $this->getChainedObjectSubjectsTree('http://www.w3.org/2000/01/rdf-schema#subPropertyOf', $propertyUri);
	}

	/**
	 * Get the first (direct) parent class URI.
	 * @param string $classUri class URI
	 * @return string Parent class URI or null (blank nodes are not included)
	 */
	public function getFirstParentClassUri($classUri)
	{
		$parentClassUri = null;
		
		$parentClassUris = $this->getParentClassUris($classUri);
		if(count($parentClassUris) > 0)
		{
			$parentClassUri = $parentClassUris[0];
		}
		
		return $parentClassUri;
	}
	
	/**
	 * Get the (direct) parent class URIs.
	 * @param string $classUri class URI
	 * @return array Array of parent class URIs (blank nodes are not included)
	 */
	public function getParentClassUris($classUri)
	{
		$parentClassUris = array();
		
		$class = $this->getResourceBySubject($classUri);
		if($class !== null)
		{
			$allParentClassUris = $class->getObjects('http://www.w3.org/2000/01/rdf-schema#subClassOf');
			foreach($allParentClassUris as $parentClassUri)
			{
				if(!preg_match('/^_:/', $parentClassUri))
				{
					$parentClassUris[] = $parentClassUri;
				}
			}
		}
		
		return $parentClassUris;
	}
	
	/**
	 * Get all (direct and indirect) parent class URIs as a list. 
	 * @param string $propertyUri class URI
	 * @return array List of parent class URIs
	 */
	public function getParentClassUriList($classUri)
	{
		return $this->getChainedSubjectObjectsList('http://www.w3.org/2000/01/rdf-schema#subClassOf', $classUri);
	}
	
	/**
	 * Get all (direct and indirect) parent class URIs as a tree. 
	 * @param string $propertyUri class URI
	 * @return array Tree of parent class URIs
	 */
	public function getParentClassUriTree($classUri)
	{
		return $this->getChainedSubjectObjectsTree('http://www.w3.org/2000/01/rdf-schema#subClassOf', $classUri);
	}
	
	/**
	 * Get the (direct) sub class URIs.
	 * @param string $classUri class URI
	 * @return array Arrary of sub class URIs
	 */
	public function getSubClassUris($classUri)
	{
		$subClassUris = array();
		
		$class = $this->getResourceBySubject($classUri);
		if($class !== null)
		{
			foreach($this->resources as $resource)
			{
				if($resource->hasStatement('http://www.w3.org/2000/01/rdf-schema#subClassOf', $classUri))
				{
					$subClassUris[] = $resource->getSubject();
				}
			}
		}
		
		return $subClassUris;
	}
	
	/**
	 * Get all (direct and indirect) sub class URIs as a list. 
	 * @param string $propertyUri class URI
	 * @return array List of sub class URIs
	 */
	public function getSubClassUriList($classUri)
	{
		return $this->getChainedObjectSubjectsList('http://www.w3.org/2000/01/rdf-schema#subClassOf', $classUri);
	}
	
	/**
	 * Get all (direct and indirect) sub class URIs as a tree. 
	 * @param string $propertyUri class URI
	 * @return array Tree of sub class URIs
	 */
	public function getSubClassUriTree($classUri)
	{
		return $this->getChainedObjectSubjectsTree('http://www.w3.org/2000/01/rdf-schema#subClassOf', $classUri);
	}
	
	/**
	 * Parse an OWL class description.
	 * warning: this is a minimal implementation! currently it only supports owl:unionOf, it always returns a flat list of URIs regardless if multiple objects are listed (meaning AND) or if a owl:unionOf is used (meaning OR)
	 * @param string $uri Uri of the OWL class description section
	 * @return array List of URIs
	 */
	protected function parseClassDescription($uri)
	{
		$results = array();
		
		if(preg_match('/^_:/', $uri))
		{
			$classResource = $this->getResourceBySubject($uri);
			if($classResource !== null)
			{
				$collectionElementUris = $classResource->getObjects('http://www.w3.org/2002/07/owl#unionOf');
				foreach($collectionElementUris as $collectionElementUri)
				{
					$listItems = $this->getRdfCollection($collectionElementUri);
					foreach($listItems as $listItem)
					{
						$results[] = $listItem;
					}
				}
			}
		}
		else
		{
			$results[] = $uri;
		}
		
		return $results;
	}
	
	/**
	 * Get the first domain URI for the property specified.
	 * @param string $propertyUri Property URI
	 * @param boolean $allowInheritance If true, also include domain URIs from parent properties
	 * @return string Domain URI
	 */
	public function getFirstDomainUri($propertyUri, $allowInheritance = true)
	{
		$domainUri = null;

		$domainUris = $this->getDomainUris($propertyUri, $allowInheritance = true);
		if(count($domainUris) > 0)
		{
			$domainUri = $domainUris[0];
		}
		
		return $domainUri;
	}

	/**
	 * Get the domain URIs for the property specified.
	 * @param string $propertyUri Property URI
	 * @param boolean $allowInheritance If true, also include domain URIs from parent properties
	 * @return array List of domain URIs
	 */
	public function getDomainUris($propertyUri, $allowInheritance = true)
	{
		$domainUris = array();
		
		$resource = $this->getResourceBySubject($propertyUri);
		
		if($resource !== null)
		{
			if($allowInheritance)
			{
				$parentPropertyUris = $this->getParentPropertyUris($propertyUri);
				foreach($parentPropertyUris as $parentPropertyUri)
				{
					$uris = $this->getDomainUris($parentPropertyUri, true);
					foreach($uris as $uri)
					{
						$domainUris[] = $uri;
					}
				}
			}
			
			$rawDomainUris = $resource->getObjects('http://www.w3.org/2000/01/rdf-schema#domain');
			foreach($rawDomainUris as $domainUri)
			{
				$uris = $this->parseClassDescription($domainUri);
				foreach($uris as $uri)
				{
					$domainUris[] = $uri;
				}
			}
		}
		
		return $domainUris;
	}
	
	/**
	 * Get the first range URI for the property specified.
	 * @param string $propertyUri Property URI
	 * @param boolean $allowInheritance If true, also include range URIs from parent properties
	 * @return string Range URI
	 */
	public function getFirstRangeUri($propertyUri, $allowInheritance = true)
	{
		$rangeUri = null;

		$rangeUris = $this->getRangeUris($propertyUri, $allowInheritance = true);
		if(count($rangeUris) > 0)
		{
			$rangeUri = $rangeUris[0];
		}
		
		return $rangeUri;
	}

	/**
	 * Get the range URIs for the property specified.
	 * @param string $propertyUri Property URI
	 * @param boolean $allowInheritance If true, also include range URIs from parent properties
	 * @return array List of range URIs
	 */
	public function getRangeUris($propertyUri, $allowInheritance = true)
	{
		$rangeUris = array();
		
		$resource = $this->getResourceBySubject($propertyUri);
		
		if($resource !== null)
		{
			if($allowInheritance)
			{
				$parentPropertyUris = $this->getParentPropertyUris($propertyUri);
				foreach($parentPropertyUris as $parentPropertyUri)
				{
					$uris = $this->getRangeUris($parentPropertyUri, true);
					foreach($uris as $uri)
					{
						$rangeUris[] = $uri;
					}
				}
			}
			
			$rawRangeUris = $resource->getObjects('http://www.w3.org/2000/01/rdf-schema#range');
			foreach($rawRangeUris as $rangeUri)
			{
				$uris = $this->parseClassDescription($rangeUri);
				foreach($uris as $uri)
				{
					$rangeUris[] = $uri;
				}
			}
		}
		
		$rangeUris = array_unique($rangeUris);
		
		return $rangeUris;
	}
	
	/**
	 * Check if the property specified is an OWL Inverse Functional Property.
	 * @param string $propertyUri Property URI
	 * @return boolean True if the property specified is an OWL Inverse Functional Property
	 */
	public function isOwlInverseFunctionalProperty($propertyUri)
	{
		$isOwlInverseFunctionalProperty = false;
		
		$rdfResource = $this->getResourceBySubject($propertyUri);

		if($rdfResource)
		{
			$isOwlInverseFunctionalProperty = $rdfResource->hasRdfType('http://www.w3.org/2002/07/owl#InverseFunctionalProperty');
		}
		
		return $isOwlInverseFunctionalProperty;
	}
}